
// babel-types参考: https://www.babeljs.cn/docs/babel-types
const codeFrame = require('@babel/code-frame').default;



// 抛出异常
function throwError(node, text, state) {

    let loc = node.loc.start;
    let line = loc.line;
    let column = loc.column;

    text = text + '. (' + line + ':' + column + ')'; 

    if (state)
    {
        text = text + '\r\n\r\n' + codeFrame(state.file.code, line, column + 1);
    }

    throw new SyntaxError(text);
}



function camlizeFn(_, x) {

    return x.toUpperCase();
}


function checkParent(node, name, state) {

    var parentName = node.openingElement.name.name;

    if (parentName !== 'switch')
    {
        if (name === 'case' || name === 'default')
        {
            throwError(node, name + ' can only in node switch', state);
        }
    }
    else if (name !== 'case' && name !== 'default')
    {
        throwError(node, name + ' can only in node switch', state);
    }
}


// 解析属性集合
function parseAttributes(outputs, node, t, state) {

    let openingElement = node.openingElement;
    let name, attributes;

    if (openingElement)
    {
        name = openingElement.name.name;
        attributes = openingElement && openingElement.attributes
    }
    else
    {
        name = '';
        attributes = [];
    }

    // 预处理子项集合
    prepareChildren(attributes, node, t, state);
    
    let length = attributes.length;

    if (length <= 0)
    {
        return null;
    }

    let item, bindings, events, key, any;

    for (let i = 0; i < length; i++)
    {
        item = attributes[i];

        if (any = parser[item.type])
        {
            if (item = any.call(parser, item, t, state))
            {
                if (key = item.key)
                {
                    any = key.value;

                    // 绑定: bind-x={...}
                    if (any.startsWith('bind-'))
                    {
                        item.value = t.arrowFunctionExpression([], item.value);

                        if (bindings)
                        {
                            bindings.push(item);
                        }
                        else
                        {
                            bindings = [item];
                        }

                        key.value = any.substring(5).replace(/-([a-z])/g, camlizeFn);
                        continue;
                    }

                    // 事件
                    if (any.startsWith('on-'))
                    {
                        if (events)
                        {
                            events.push(item);
                        }
                        else
                        {
                            events = [item];
                        }

                        key.value = any.substring(3).replace(/-([a-z])/g, camlizeFn);
                        continue;
                    }

                    key.value = any.replace(/-([a-z])/g, camlizeFn);
                }

                outputs.push(item);
            }
        }
        else
        {
            outputs.push(item);
        }
    }

    if (bindings)
    {
        outputs.push(t.objectProperty(
            t.stringLiteral('bindings'),
            t.objectExpression(bindings)
        ));
    }

    if (events)
    {
        outputs.push(t.objectProperty(
            t.stringLiteral('events'),
            t.objectExpression(events)
        ));
    }

    return t.objectExpression(outputs);
}


// 预处理子项集合
function prepareChildren(attributes, node, t, state) {

    let children = node.children;
    let length = children.length;
    let flag = 0;
    let index = 0;
    let slots;

    while (index < length)
    {
        let item = children[index++];

        switch (item.type)
        {
            case 'JSXText': // 去除空行
                if (/^\s*$/.test(item.value))
                {
                    length--;
                    children.splice(--index, 1);
                }
                else if ((flag & 2) === 2)
                {
                    throwError(item, 'JSXText cannot be on the same level as JSXElement', state);
                }
                else
                {
                    flag |= 1;
                }
                break;

            case 'JSXExpressionContainer': // 去除注释
                // 注释
                if (item.expression.type === 'JSXEmptyExpression')
                {
                    length--;
                    children.splice(--index, 1);
                }
                else if ((flag & 2) === 2)
                {
                    throwError(item, 'JSXExpressionContainer cannot be on the same level as JSXElement', state);
                }
                else
                {
                    flag |= 1;
                }
                break;

            case 'JSXElement': // 节点
                let openingElement = item.openingElement;
                let name = openingElement.name;

                if (name.type === 'JSXNamespacedName')
                {
                    throwError(node, 'Namespace tags are not supported', state);
                }

                name = name.name;

                // 检查父节点
                if (node.openingElement)
                {
                    checkParent(node, name, state);
                }

                if (name === 'slot-into') // 处理插槽节点
                {
                    slots || attributes.push(t.objectProperty(
                        t.stringLiteral('__set_slots'),
                        t.objectExpression(slots = [])
                    ));

                    parseSlotIntoNode(slots, item, t, state);

                    length--;
                    children.splice(--index, 1);
                }
                else if ((flag & 1) === 1)
                {
                    throwError(item, 'JSXElement cannot be on the same level as JSXText or JSXExpressionContainer', state);
                }
                else
                {
                    flag |= 2;
                }
                break;
        }
    }
}



function parseSlotIntoNode(slots, node, t, state) {

    let items = node.openingElement.attributes;

    // 预处理子项集合
    prepareChildren(items, node, t, state);

    if (!items[0] || items[0].name.name !== 'name')
    {
        throwError(item, '"slot-into" node can have only one "name" attribute', state);
    }

    let children = parseChildren(node, 'slot-into', t, state);

    if (!children)
    {
        throwError(node, '"slot-info" node must contain at least one child node', state);
    }

    slots.push(t.objectProperty(
        items[0].value,
        children
    ));
}


// 解析子项集合
function parseChildren(node, tagName, t, state) {

    let children = node.children;
    let flag = 0;
    let items;

    for (let i = 0, l = children.length; i < l; i++)
    {
        let item = children[i];
        let type = item.type;

        if (type === 'JSXExpressionContainer')
        {
            flag |= 1;
            item = item.expression;
        }
        else if (type === 'JSXText')
        {
            flag |= 2;
            item = t.stringLiteral(item.value);
        }
        else
        {
            item = parser[type](item, t, state);

            if (!item)
            {
                continue;
            }
        }

        if (items)
        {
            items.push(item);
        }
        else
        {
            items = [item];
        }
    }

    if (!items)
    {
        return;
    }

    let length = items.length;

    // for和script节点必须且只能包含一个JSXExpressionContainer子节点
    if ((length > 1 || flag !== 1) && (tagName === 'for' || tagName === 'script'))
    {
        throwError(node, '"' + tagName + '" node must and can only contain one JSXExpressionContainer child node');
    }

    if (length === 1)
    {
        return flag ? items[0] : t.arrayExpression(items);
    }

    // 多个表达拼成字符串
    if (flag)
    {
        return t.callExpression(
            t.memberExpression(
                t.arrayExpression(items),
                t.identifier('join')
            ),
            [t.stringLiteral('')]
        );
    }

    return t.arrayExpression(items);
}



const parser = {


    // <>[JSXChildren]</>
    JSXFragment(node, t, state) {

        let name = t.stringLiteral('fragment');
        let attributes = parseAttributes([], node, t, state);
        let children;

        if (children = parseChildren(node, '<>', t, state))
        {
            return t.arrayExpression([
                name, 
                attributes || t.nullLiteral(),
                children
            ]);
        }

        return t.arrayExpression(attributes ? [name, attributes] : [name]);
    },


    // JSXSelfClosingElement
    // JSXOpeningElement [JSXChildren] JSXClosingElement
    JSXElement(node, t, state) {
        
        let name = node.openingElement.name;
        let tagName = name.name;

        if (name.type === 'JSXIdentifier')
        {
            // 大写字母开头为Class形式
            if (tagName >= 'A' && tagName <= 'Z') 
            {
                name = t.identifier(tagName);
            }
            else // 否则为字符串形式
            {
                name = t.stringLiteral(tagName);
            }
        }
        else
        {
            name = this[name.type](name, t, state);
        }

        let attributes = parseAttributes([], node, t, state);
        let children = parseChildren(node, tagName, t, state);

        if (!children)
        {
            return t.arrayExpression(attributes ? [name, attributes] : [name]);
        }

        return t.arrayExpression([
            name,
            attributes || t.nullLiteral(),
            children
        ]);
    },


    // IdentifierStart
    // JSXIdentifier IdentifierPart
    // JSXIdentifier NO WHITESPACE OR COMMENT -
    JSXIdentifier(node, t, state) {

        return t.identifier(node.name);
    },


    // JSXIdentifier.JSXIdentifier
    // JSXMemberExpression.JSXIdentifier
    JSXMemberExpression(node, t, state) {
        
        let object = node.object;
        let property = node.property;

        object = this[object.type](object, t, state);
        property = this[property.type](property, t, state);

        return t.memberExpression(object, property, false, null);
    },


    // <t>{}</t>
    JSXExpressionContainer(node, t, state) {

        return node.expression;
    },


    // { ... AssignmentExpression }
    JSXSpreadAttribute(node, t, state) {
        
        return t.spreadElement(node.expression || node.argument);
    },


    // JSXAttributeName [JSXAttributeInitializer]
    JSXAttribute(node, t, state) {
        
        let name = node.name;
        let value = node.value;
        let fn;

        if (name.type === 'JSXNamespacedName')
        {
            throwError(node, 'Namespace attributes are not supported', state);
        }
        
        name = name.name;

        if (value = node.value)
        {
            if (fn = this[value.type])
            {
                value = fn.call(this, node.value, t, state);
            }
        }
        else // 无值默认为{true}
        {
            return t.objectProperty(
                t.stringLiteral(name), 
                t.booleanLiteral(true)
            );
        }

        if (name === 'ref')
        {
            return t.objectProperty(
                t.stringLiteral('ref'),
                t.arrowFunctionExpression(
                    [t.identifier('current')],
                    t.assignmentExpression(
                        '=', 
                        value, 
                        t.identifier('current')
                    )
                )
            );
        }

        return t.objectProperty(
            t.stringLiteral(name), 
            value
        );
    },


    // ...
    JSXSpreadChild(node, t, state) {
        
        return t.spreadElement(node.expression);
    },


    // JSXTextCharacter [JSXText]
    JSXText(node, t, state) {
        
        return t.stringLiteral(node.value);
    },


    // JSXIdentifier: JSXIdentifier
    JSXNamespacedName(node, t, state) {

        throwError(node, 'Namespace tags are not supported', state);
    }


}




module.exports = function ({ types }) {

    
    return {
        
        name: 'babel-plugin-jsx',

        inherits: require('@babel/plugin-syntax-jsx').default,

        visitor: {

            
            // root
            // <>[JSXChildren]</>
            JSXFragment(path, state) {

                let t = types;
                let node = path.node;
                let attributes = parseAttributes([], node, t, state);
                let children = parseChildren(node, '<>', t, state);
                let elements;

                if (children)
                {
                    elements = [
                        t.nullLiteral(),
                        attributes || t.nullLiteral(),
                        children
                    ];
                }
                else if (attributes)
                {
                    elements = [
                        t.nullLiteral(),
                        attributes
                    ];
                }
                else
                {
                    elements = [t.nullLiteral()];
                }

                path.replaceWith(t.arrayExpression(elements));
            },


            // root
            // JSXSelfClosingElement
            // JSXOpeningElement [JSXChildren] JSXClosingElement
            JSXElement(path, state) {

                let node = path.node;

                node = parser.JSXElement(node, types, state);
                path.replaceWith(node);
            },
            

        }
    }
}
